home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Games of Daze
/
Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso
/
djgpp
/
contrib
/
sblaster
/
sb.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-11-16
|
11KB
|
552 lines
/*
* Play digitized sound sample on soundblaster DAC using DMA.
* This source code is in the public domain.
*
* Modification History
*
* 9-Nov-93 David Baggett Wrote it based on Sound Blaster
* <dmb@ai.mit.edu> Freedom project and Linux code.
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <go32.h>
#include <dos.h>
#include <dpmi.h>
#include <string.h>
#include <pc.h>
#include "sb.h"
#define _PROTO_(x) x
#include "proto.h"
/*
* Define TEST to make an executable (i.e., compile main).
*/
#define TEST
/*
* GO32 DPMI structs for accessing DOS memory.
*/
static _go32_dpmi_seginfo dosmem; /* DOS (conventional) memory buffer */
static _go32_dpmi_seginfo oldirq_rm; /* original real mode IRQ */
static _go32_dpmi_registers rm_regs;
static _go32_dpmi_seginfo rm_si; /* real mode interrupt segment info */
static _go32_dpmi_seginfo oldirq_pm; /* original prot-mode IRQ */
static _go32_dpmi_seginfo pm_si; /* prot-mode interrupt segment info */
/*
* Card parameters
*/
static unsigned int sb_ioaddr;
static unsigned int sb_irq;
static unsigned int sb_dmachan;
/*
* Is a sound currently playing?
*/
static volatile int sb_sound_playing = 0;
/*
* Conventional memory buffers for DMA.
*/
static volatile int sb_bufnum = 0;
static char *sb_buf[2];
static unsigned int sb_buflen[2];
/*
* Info about current sample
*/
static unsigned char *sb_curdata; /* pointer to next bit of data */
static unsigned long sb_curlength; /* total length length left to play */
/*
* DMA chunk size, in bytes.
*
* This parameter determines how big our DMA buffers are. We play
* the sample by piecing together chunks that are this big. This
* means that we don't have to copy the entire sample down into
* conventional memory before playing it. (A nice side effect of
* this is that we can play samples that are longer than 64K.)
*
* Setting this is tricky. If it's too small, we'll get lots
* of interrupts, and slower machines might not be able to keep
* up. Furthermore, the smaller this is, the more grainy the
* sound will come out.
*
* On the other hand, if we make it too big there will be a noticeable
* delay between a call to sb_play and when the sound actually starts
* playing, which is unacceptable for things like games where sound
* effects should be "instantaneous".
*
*/
#define DMA_CHUNK (2048)
/*
* Define replacements for DOS enable and disable.
* Be careful about inlining these -- GCC has a tendency to move
* them around even if you declare them volatile. (This is definitely
* true before 2.5.2; may be fixed in 2.5.2.)
*/
void
disable()
{
__asm__ __volatile__ ("cli");
}
void
enable()
{
__asm__ __volatile__ ("sti");
}
/*
* Interrupt handler
*
* This is called in both protected mode and in real mode -- this means
* we don't have to switch modes when we service the interrupt.
*/
void
sb_intr(_go32_dpmi_registers *reg)
{
register unsigned n = sb_bufnum; /* buffer we just played */
/*
* Acknowledge soundblaster
*/
inportb(sb_ioaddr + SB_DSP_DATA_AVAIL);
/*
* Start next buffer player
*/
sb_play_buffer(1 - n);
/*
* Fill this buffer for next time around
*/
sb_fill_buffer(n);
/*
* Acknowledge the interrupt
*/
outportb(0x20, 0x20);
enable();
}
/*
* Fill buffer n with the next data.
*/
void
sb_fill_buffer(register unsigned n)
{
if (sb_curlength > DMA_CHUNK) {
sb_buflen[n] = DMA_CHUNK;
dosmemput(sb_curdata, DMA_CHUNK, (unsigned long) sb_buf[n]);
sb_curlength -= DMA_CHUNK;
sb_curdata += DMA_CHUNK;
}
else if (sb_curlength == 0) {
sb_buflen[n] = 0;
sb_curlength = 0;
}
else {
sb_buflen[n] = sb_curlength;
dosmemput(sb_curdata, sb_curlength, (unsigned long) sb_buf[n]);
sb_curdata += sb_curlength;
sb_curlength = 0;
}
}
void
sb_play_buffer(register unsigned n)
{
int t;
unsigned char im, tm;
/*
* See if we're already done
*/
if (sb_buflen[n] == 0) {
sb_sound_playing = 0;
return;
}
disable();
/*
* Enable interrupts on PIC
*/
im = inportb(0x21);
tm = ~(1 << sb_irq);
outportb(0x21,im & tm);
/*
* Set DMA mode
*/
outportb(SB_DMA_MASK, 5);
outportb(SB_DMA_FF, 0);
outportb(SB_DMA_MODE, 0x49);
/*
* Set transfer address
*/
sb_bufnum = n;
t = (int) ((unsigned long) sb_buf[n] >> 16);
outportb(SB_DMAPAGE + 3, t);
t = (int) ((unsigned long) sb_buf[n] & 0xFFFF);
outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan, t >> 8);
/*
* Set transfer length byte count
*/
outportb(SB_DMA + 2 * sb_dmachan + 1, sb_buflen[n] & 0xFF);
outportb(SB_DMA + 2 * sb_dmachan + 1, sb_buflen[n] >> 8);
/*
* Unmask DMA channel
*/
outportb(SB_DMA_MASK, sb_dmachan);
enable();
sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */
/* sb_write length */
sb_writedac(sb_buflen[n] & 0xFF);
sb_writedac(sb_buflen[n] >> 8);
/*
* A sound is playing now.
*/
sb_sound_playing = 1;
}
/*
* Set sampling/playback rate.
* Parameter is rate in Hz (samples per second).
*/
void
sb_set_sample_rate(unsigned int rate)
{
unsigned char tc = (unsigned char) (256 - 1000000/rate);
sb_writedac(SB_TIME_CONSTANT); /* Command byte for sample rate */
sb_writedac(tc); /* Sample rate time constant */
}
void
sb_voice(int state)
{
sb_writedac(state ? SB_SPEAKER_ON : SB_SPEAKER_OFF);
}
/*
* Read soundblaster card parameters from BLASTER enivronment variable.
*/
void
sb_getparams()
{
char *t, *blaster;
/*
* Set arguments to Soundblaster defaults
*/
sb_ioaddr = 0x220;
sb_irq = 7;
sb_dmachan = 1;
t = getenv("BLASTER");
if (!t)
return;
/*
* Get a copy
*/
blaster = strdup(t);
/*
* Parse the BLASTER variable
*/
t = strtok(blaster, " \t");
while (t) {
switch (t[0]) {
case 'A':
case 'a':
/* I/O address */
sscanf(t + 1, "%x", &sb_ioaddr);
break;
case 'I':
case 'i':
/* IRQ */
sb_irq = atoi(t + 1);
break;
case 'D':
case 'd':
/* DMA channel */
sb_dmachan = atoi(t + 1);
break;
case 'T':
case 't':
/* what is this? */
break;
default:
printf("Unknown BLASTER option %c\n",t[0]);
break;
}
t = strtok(NULL," \t");
}
free(blaster);
return;
}
/*
* Init the soundblaster card.
*/
void
sb_initcard()
{
outportb(sb_ioaddr + SB_DSP_RESET, 1);
/*
* Kill some time
*/
inportb(sb_ioaddr + SB_DSP_RESET);
inportb(sb_ioaddr + SB_DSP_RESET);
inportb(sb_ioaddr + SB_DSP_RESET);
inportb(sb_ioaddr + SB_DSP_RESET);
outportb(sb_ioaddr + SB_DSP_RESET, 0);
/*
* Need to add a timeout here!
*/
while (inportb(sb_ioaddr + SB_DSP_READ_DATA) != 0xAA)
;
}
/*
* Install our interrupt as the real mode interrupt handler for
* the IRQ the soundblaster is on.
*
* We accomplish this by have GO32 allocate a real mode callback for us.
* The callback packages our protected mode code up in a real mode wrapper.
*/
void
sb_install_rm_interrupt()
{
int ret;
rm_si.pm_offset = (int) sb_intr;
ret = _go32_dpmi_allocate_real_mode_callback_iret(&rm_si, &rm_regs);
if (ret != 0) {
printf("cannot allocate real mode callback, error=%04x\n",ret);
exit(1);
}
#ifdef TEST
printf("real mode callback is at %04x:%04x\n",
rm_si.rm_segment, rm_si.rm_offset);
#endif
/*
* Install our real mode interrupt handler
*/
disable();
_go32_dpmi_get_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &rm_si);
enable();
}
/*
* Remove our real mode interrupt handler.
*/
void
sb_cleanup_rm_interrupt()
{
disable();
_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
_go32_dpmi_free_real_mode_callback(&rm_si);
enable();
}
/*
* Install our interrupt as the protected mode interrupt handler for
* the IRQ the soundblaster is on.
*/
void
sb_install_pm_interrupt()
{
disable();
_go32_dpmi_get_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
pm_si.pm_offset = (int) sb_intr;
pm_si.pm_selector = _go32_my_cs();
_go32_dpmi_chain_protected_mode_interrupt_vector(8 + sb_irq, &pm_si);
enable();
}
/*
* Remove our protected mode interrupt handler.
*/
void
sb_cleanup_pm_interrupt()
{
disable();
_go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
enable();
}
/*
* Allocate conventional memory for our DMA buffers.
* Each DMA buffer must be aligned on a 64K boundary in physical memory.
*/
void
sb_init_buffers()
{
dosmem.size = 65536*3/16;
if (_go32_dpmi_allocate_dos_memory(&dosmem)) {
printf("Unable to allocate dos memory - max size is %d\n", dosmem.size);
exit(1);
}
#ifdef TEST
printf("dos buffer at 0x%04x:0\n", dosmem.rm_segment);
#endif
(unsigned long) sb_buf[0] = dosmem.rm_segment * 16;
(unsigned long) sb_buf[0] += 0x0FFFFL;
(unsigned long) sb_buf[0] &= 0xFFFF0000L;
(unsigned long) sb_buf[1] = (unsigned long) sb_buf[0] + 0x10000;
#ifdef TEST
printf("DMA buffers at physical 0x%0x and 0x%0x\n",
(unsigned int) sb_buf[0], (unsigned int) sb_buf[1]);
#endif
}
/*
* Initliaze our internal buffers and the card itself to prepare
* for sample playing.
*
* Call this once per program, not once per sample.
*/
void
sb_init()
{
/*
* Card card params and initialize card.
*/
sb_getparams();
sb_initcard();
/*
* Install our interrupt handlers
*/
sb_install_rm_interrupt();
sb_install_pm_interrupt();
/*
* Allocate buffers in conventional memory for double-buffering
*/
sb_init_buffers();
}
/*
* Restore card and system to sane state before exiting.
*/
void
sb_cleanup()
{
/*
* Remove our interrupt handlers
*/
sb_cleanup_rm_interrupt();
sb_cleanup_pm_interrupt();
}
/*
* Play a sample through the DAC using DMA.
*/
void
sb_play(unsigned char *data, unsigned long length)
{
/*
* Prime the buffers
*/
sb_curdata = data;
sb_curlength = length;
sb_fill_buffer(0);
sb_fill_buffer(1);
/*
* Start the first buffer playing.
*/
sb_play_buffer(0);
}
#ifdef TEST
void
main(argc, argv)
int argc;
char **argv;
{
unsigned long length;
unsigned char *data;
FILE *fp;
struct stat statbuf;
if (argc < 3) {
printf("usage: sb sample.sam sample-rate\n");
printf("sample-rate is in hertz (e.g., 11000)\n");
exit(0);
}
if (stat(argv[1], &statbuf) < 0) {
printf("%s: can't stat %s\n", argv[0], argv[1]);
exit(1);
}
length = statbuf.st_size;
data = calloc(length, 1);
if (!data) {
printf("%s: out of memory\n", argv[0]);
exit(1);
}
fp = fopen(argv[1], "rb");
if (!fp) {
printf("%s: can't open %s\n", argv[0], argv[1]);
exit(1);
}
if (fread(data, 1, length, fp) < length) {
printf("%s: error reading %s\n", argv[0], argv[1]);
exit(1);
}
sb_init();
printf("I/O addr = %x, IRQ = %d, DMA channel = %d\n",
sb_ioaddr, sb_irq, sb_dmachan);
sb_voice(1);
sb_set_sample_rate(atoi(argv[2]));
sb_play(data, length);
while (sb_sound_playing)
;
sb_cleanup();
exit(0);
}
#endif